use glium::glutin;

use glutin::event::{Event, WindowEvent};
use glutin::event_loop::ControlFlow;
use glutin::WindowedContext;

use glium::{Surface, IndexBuffer, VertexBuffer};
use glium::implement_vertex;
use glium::index::PrimitiveType;

use cgmath::{Matrix4, Point3, Vector3};

#[derive(Copy, Clone)]
pub struct Vertex {
    position: [f32; 3],
    normal: [f32; 3],
    color: [f32; 3]
}

implement_vertex!(Vertex, position, normal, color);

macro_rules! def_vertex {
    ($($x: expr, $y: expr, $z: expr, $nx: expr, $ny: expr, $nz: expr, $r: expr, $g: expr, $b: expr),+) => {
        vec![
            $(Vertex { position: [$x, $y, $z], normal: [$nx, $ny, $nz], color: [$r, $g, $b] }), *
        ]
    };
}

macro_rules! ccp {
    ($x: expr, $y: expr, $z: expr) => {
        Point3 { x: $x, y: $y, z: $z }
    };
}

macro_rules! ccv {
    ($x: expr, $y: expr, $z: expr) => {
        Vector3 { x: $x, y: $y, z: $z }
    };
}

fn main() {
    let el = glutin::event_loop::EventLoop::new();
    let display = {
        let wb = glutin::window::WindowBuilder::new()
            .with_inner_size(glutin::dpi::LogicalSize::new(800.0, 600.0))
            .with_title("Lighting Test");
        let cb = glutin::ContextBuilder::new().with_depth_buffer(24);
        glium::Display::new(wb, cb, &el).unwrap()
    };

    let shape = def_vertex!{
        // POSITION         NORMAL           COLOR
        // FRONT
         0.5, -0.5,  0.5,   0.0, 0.0, 1.0,   1.0, 1.0, 1.0,
        -0.5, -0.5,  0.5,   0.0, 0.0, 1.0,   1.0, 1.0, 1.0,
        -0.5,  0.5,  0.5,   0.0, 0.0, 1.0,   1.0, 1.0, 1.0,
         0.5,  0.5,  0.5,   0.0, 0.0, 1.0,   1.0, 1.0, 1.0,

        // LEFT
        -0.5, -0.5,  0.5,  -1.0, 0.0, 0.0,   1.0, 1.0, 1.0,
        -0.5,  0.5,  0.5,  -1.0, 0.0, 0.0,   1.0, 1.0, 1.0,
        -0.5,  0.5, -0.5,  -1.0, 0.0, 0.0,   1.0, 1.0, 1.0,
        -0.5, -0.5, -0.5,  -1.0, 0.0, 0.0,   1.0, 1.0, 1.0,

        // BACK
        -0.5, -0.5, -0.5,   0.0, 0.0,-1.0,   1.0, 1.0, 1.0,
         0.5, -0.5, -0.5,   0.0, 0.0,-1.0,   1.0, 1.0, 1.0,
         0.5,  0.5, -0.5,   0.0, 0.0,-1.0,   1.0, 1.0, 1.0,
        -0.5,  0.5, -0.5,   0.0, 0.0,-1.0,   1.0, 1.0, 1.0,

        // RIGHT
         0.5, -0.5, -0.5,   1.0, 0.0, 0.0,   1.0, 1.0, 1.0,
         0.5, -0.5,  0.5,   1.0, 0.0, 0.0,   1.0, 1.0, 1.0,
         0.5,  0.5,  0.5,   1.0, 0.0, 0.0,   1.0, 1.0, 1.0,
         0.5,  0.5, -0.5,   1.0, 0.0, 0.0,   1.0, 1.0, 1.0,

        // TOP
         0.5,  0.5,  0.5,   0.0, 1.0, 0.0,   1.0, 1.0, 1.0,
        -0.5,  0.5,  0.5,   0.0, 1.0, 0.0,   1.0, 1.0, 1.0,
        -0.5,  0.5, -0.5,   0.0, 1.0, 0.0,   1.0, 1.0, 1.0,
         0.5,  0.5, -0.5,   0.0, 1.0, 0.0,   1.0, 1.0, 1.0,

        // BOTTOM
         0.5, -0.5, -0.5,   0.0,-1.0, 0.0,   1.0, 1.0, 1.0,
        -0.5, -0.5, -0.5,   0.0,-1.0, 0.0,   1.0, 1.0, 1.0,
        -0.5, -0.5,  0.5,   0.0,-1.0, 0.0,   1.0, 1.0, 1.0,
         0.5, -0.5,  0.5,   0.0,-1.0, 0.0,   1.0, 1.0, 1.0
    };

    let indices: Vec<u32> = vec![
        0, 1, 3,
        2, 3, 1,

        4, 7, 5,
        6, 5, 7,

        8, 9, 11,
        10, 11, 9,
        
        12, 13, 15,
        14, 15, 13,

        16, 17, 19,
        18, 19, 17,

        20, 21, 23,
        22, 23, 21
    ];

    let vbo = VertexBuffer::new(&display, &shape).unwrap();
    let ibo = IndexBuffer::new(&display, PrimitiveType::TrianglesList, &indices).unwrap();

    let params = glium::DrawParameters {
        depth: glium::Depth {
            test: glium::draw_parameters::DepthTest::IfLess,
            write: true,
            .. Default::default()
        },
        .. Default::default()
    };

    let proj: [[f32; 4]; 4] = cgmath::perspective(cgmath::Deg(45.0), 4.0 / 3.0, 0.1, 100.0).into();
    let view: [[f32; 4]; 4] = Matrix4::look_at(ccp!(0.0, 0.0, 3.0), ccp!(0.0, 0.0, 2.0), ccv!(0.0, 1.0, 0.0)).into();
    let mut model_mat = Matrix4::from_angle_x(cgmath::Deg(22.5)) * Matrix4::from_angle_y(cgmath::Rad(45.0));

    let shader = glium::Program::from_source(&display, include_str!("vert.glsl"), include_str!("frag.glsl"), None).unwrap();

    let mut theta = 0.0;
    let mut now = std::time::SystemTime::now();
    let mut delta_t = 0.0f32;

    display.gl_window().window().set_cursor_visible(false);
    
    el.run(move |event, _, cf| {
        *cf = ControlFlow::Poll;
        match event {
            Event::NewEvents(_) => {
                delta_t = now.elapsed().unwrap().as_secs_f32();
                now = std::time::SystemTime::now();
            },
            Event::WindowEvent {
                event,
                ..
            } => {
                match event {
                    WindowEvent::CloseRequested => *cf = ControlFlow::Exit,
                    _ => (),
                }
            }
            Event::MainEventsCleared => {
                theta += std::f32::consts::FRAC_PI_4 * delta_t;

                // draw 
                let mut target = display.draw();
                target.clear_color_and_depth((0.0, 0.0, 0.0, 1.0), 1.0);

                

                let model: [[f32; 4]; 4] = model_mat.into();

                let uniform = glium::uniform!{
                    proj: proj,
                    view: view,
                    model: model,
                    lightPos: [2.0 * f32::cos(theta), 1.2, 2.0 * f32::sin(theta)]
                };

                target.draw(&vbo, &ibo, &shader, &uniform, &params).unwrap();

                target.finish().unwrap();
            },
            _ => (),
        }
    });
}